package com.jinglu.cloud.security.utils;

import cn.hutool.core.collection.CollectionUtil;
import com.jinglu.cloud.security.enums.UserDetailResponseType;
import com.jinglu.cloud.security.model.TokenInfo;
import com.jinglu.cloud.security.model.UserDetailResponse;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.*;

/**
 * @Author lvzhenzhao
 * @Datetime 2021/03/02-下午3:22
 */
@Slf4j
@Component
public class JwtTokenUtil implements Serializable {

    private static final transient Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);
    private static final String CLAIM_KEY_AUDIENCE = "aud";
    private static final String CLAIM_KEY_CREATED = "created";
    private static final String CLAIM_KEY_DEVICE_ID = "deviceId";
    private static final String CLAIM_KEY_APP_ID = "appId";
    private static final String AUDIENCE_UNKNOWN = "unknown";
    private static final String AUDIENCE_WEB = "web";
    private static final String AUDIENCE_MOBILE = "mobile";
    private static final String AUDIENCE_TABLET = "tablet";
    public static final String TOKEN_TYPE_BEARER = "Bearer";
    public static final String REDIS_PREFIX_AUTH = "auth:";
    public static final String REDIS_PREFIX_USER = "user-details:";
    private static final String OFFICIAL_ISSUER = "dxq.com";

    @Autowired
    private RedisRepository redisRepository;

    @Value("${jwt.secret:123456}")
    private String secret;

    @Value("${jwt.expiration:604800}")
    private Long expiration;


    /**
     * 生成 Token
     *
     * @param userDetails 用户信息
     * @return String
     */
    public String generateToken(UserDetails userDetails) {
        String key = REDIS_PREFIX_AUTH + userDetails.getUsername();
        String token;
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_CREATED, new Date());
        token = Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername())
                .setAudience(AUDIENCE_WEB).setIssuer(JwtTokenUtil.OFFICIAL_ISSUER).setExpiration(generateExpirationDate(expiration))
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        TokenInfo tokenInfo = new TokenInfo();
        tokenInfo.setToken(token);
        tokenInfo.setLoginName(userDetails.getUsername());
        redisRepository.setExpire(key, new Gson().toJson(tokenInfo), expiration);
//            token = new Gson().fromJson(redisRepository.get(key), TokenInfo.class).getToken();
        //userDetails.setPassword(null);
        putUserDetails(userDetails, null, expiration);
        return token;
    }

    /**
     * 获取token字符串
     *
     * @param userName
     * @return
     */
    public String getTokenStr(String userName) {
        String key = REDIS_PREFIX_AUTH + userName;
        String tokenJson = redisRepository.get(key);
        if (StringUtils.isEmpty(tokenJson)) {
            return "";
        }
        return new Gson().fromJson(redisRepository.get(key), TokenInfo.class).getToken();
    }

    /**
     * 存储用户权限相关信息
     *
     * @param userDetails 用户信息
     * @param ttl         过期时间
     */
    public void putUserDetails(UserDetails userDetails, String userDeviceId, Long ttl) {
        // key=当前用户归属机构ID:user-details:loginName
        //String key = userDetails.getInTenantId() + ":" + REDIS_PREFIX_USER + userDetails.getUsername()+":"+userDeviceId;
        String key = REDIS_PREFIX_USER + userDetails.getUsername();
        redisRepository.setExpire(key, JSON.toJSONString(userDetails), ttl);
    }

    /**
     * 设置用户权限相关信息
     *
     * @param userDetails
     */
    public void setUserDetails(UserDetails userDetails) {
        putUserDetails(userDetails, null, expiration);
    }


    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = this.getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception var4) {
            username = null;
        }

        return username;
    }

    public String getDeviceIdFromToken(String token) {
        Claims claims = this.getClaimsFromToken(token);
        return claims != null ? claims.get("deviceId").toString() : null;
    }

    public String getAppIdFromToken(String token) {
        Claims claims = this.getClaimsFromToken(token);
        return claims != null ? claims.get("appId").toString() : null;
    }

    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            Claims claims = this.getClaimsFromToken(token);
            created = new Date((Long) claims.get("created"));
        } catch (Exception var4) {
            created = null;
        }

        return created;
    }

    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            Claims claims = this.getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception var4) {
            expiration = null;
        }

        return expiration;
    }

    public String getAudienceFromToken(String token) {
        String audience;
        try {
            Claims claims = this.getClaimsFromToken(token);
            audience = (String) claims.get("aud");
        } catch (Exception var4) {
            audience = null;
        }

        return audience;
    }

    public Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = (Claims) Jwts.parser().requireIssuer(OFFICIAL_ISSUER).setSigningKey(this.secret).parseClaimsJws(token).getBody();
        } catch (Exception var4) {
            claims = null;
        }

        return claims;
    }

    private Date generateExpirationDate(long expireSeconds) {
        return new Date(System.currentTimeMillis() + expireSeconds * 1000L);
    }

    private Boolean isTokenExpired(String token) {
        Date expiration = this.getExpirationDateFromToken(token);
        return expiration == null ? false : expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return lastPasswordReset != null && created.before(lastPasswordReset);
    }

    private Boolean ignoreTokenExpiration(String token) {
        String audience = this.getAudienceFromToken(token);
        return "tablet".equals(audience) || "mobile".equals(audience);
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        Date created = this.getCreatedDateFromToken(token);
        return !this.isCreatedBeforeLastPasswordReset(created, lastPasswordReset) && (!this.isTokenExpired(token) || this.ignoreTokenExpiration(token));
    }

    public UserDetailResponse getAuthUserString(String token) {
        String username = this.getUsernameFromToken(token);
        logger.info("username:" + username);
        String tokenKey = "auth:" + username;
        String tokenKeyValue = this.redisRepository.get(tokenKey);
        TokenInfo tokenInfo = (TokenInfo) (new Gson()).fromJson(tokenKeyValue, TokenInfo.class);
        logger.info("tokenKeyValue:" + tokenKeyValue);
        logger.info("token:" + token);
        if (null != tokenInfo && token.equals(tokenInfo.getToken())) {
            String userDetailKey = "user-details:" + username;
            String userDetailKeyValue = this.redisRepository.get(userDetailKey);
            if (null == userDetailKeyValue) {
                logger.error("userDetailKeyValue为null");
                return new UserDetailResponse(false, UserDetailResponseType.REDIRECT.getCode());
            } else {
                return new UserDetailResponse(true, UserDetailResponseType.SUCCESS.getCode(), userDetailKeyValue);
            }
        } else {
            logger.error("token不存在");
            return new UserDetailResponse(false, UserDetailResponseType.INVALID_TOKEN.getCode());
        }
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        String username = this.getUsernameFromToken(token);
        return username.equals(userDetails.getUsername()) && !this.isTokenExpired(token);
    }

    public void batchDelUserDetailKey(List<String> usernameList) {
        if (!CollectionUtil.isEmpty(usernameList)) {
            Iterator var2 = usernameList.iterator();

            while (var2.hasNext()) {
                String username = (String) var2.next();
                String userDetailKey = "user-details:" + username;
                this.redisRepository.del(new String[]{userDetailKey});
            }

        }
    }

    public void batchDelUserDetailKey() {
        this.redisRepository.deleteByPrex("user-details:");
    }

    public void batchDelUserTokenKey(List<String> usernameList) {
        if (!CollectionUtil.isEmpty(usernameList)) {
            Iterator var2 = usernameList.iterator();

            while (var2.hasNext()) {
                String username = (String) var2.next();
                String userDetailKey = "user-details:" + username;
                String tokenKey = "auth:" + username;
                this.redisRepository.del(new String[]{tokenKey});
                this.redisRepository.del(new String[]{userDetailKey});
            }

        }
    }

    public Boolean validateToken(String token) {
        if (StringUtils.isEmpty(token)) {
            return false;
        } else {
            String username = this.getUsernameFromToken(token);
            if (StringUtils.isEmpty(username)) {
                return false;
            } else {
                String deviceId = this.getDeviceIdFromToken(token);
                String key = deviceId + ":" + "auth:" + username;
                String redisTokenInfo = this.redisRepository.get(key);
                Gson gson = new Gson();
                TokenInfo tokenInfo = (TokenInfo) gson.fromJson(redisTokenInfo, TokenInfo.class);
                return !this.isTokenExpired(token) && tokenInfo != null && token.equals(tokenInfo.getToken());
            }
        }
    }

    public TokenInfo getTokenInfo(String token) {
        String username = this.getUsernameFromToken(token);
        String deviceId = this.getDeviceIdFromToken(token);
        String tokenKey = deviceId + ":" + "auth:" + username;
        String tokenKeyValue = this.redisRepository.get(tokenKey);
        return (TokenInfo) (new Gson()).fromJson(tokenKeyValue, TokenInfo.class);
    }

    public boolean deleteTokenCache(String username) {
        String tokenKeySuffix = "auth:" + username;
        this.redisRepository.deleteBySuffix(tokenKeySuffix);
        this.redisRepository.deleteBySuffix("user-details:" + username);
        return true;
    }

    public boolean deleteAuthCache(String username) {
        String userDetailKeySuffix = "user-details:" + username;
        this.redisRepository.deleteBySuffix(userDetailKeySuffix);
        return true;
    }

    public boolean deleteAuthCacheByTenantId(long tenantId) {
        String userdetailPrefix = tenantId + ":" + "user-details:";
        this.redisRepository.deleteByPrex(userdetailPrefix);
        return true;
    }
}
